import stringWidth from "string-width";


export class StrUtil {

    /**
     * 将 Git 仓库地址从 HTTP(S) 转换为 SSH 格式
     * @param {string} url Git 仓库地址
     * @returns {string} 转换后的地址
     */
    static toSshUrl(url) {
        if (!url) return url;

        // 如果已经是 ssh 格式
        if (url.startsWith("git@")) {
            return url;
        }

        // 匹配 http/https
        const match = url.match(/^https?:\/\/([^/]+)\/(.+)$/);
        if (match) {
            const host = match[1];  // gitlab.inzwc.com
            const path = match[2];  // hst-base/hs-hk-dubbo-base
            const normalizedPath = path.endsWith('.git') ? path : `${path}.git`;
            return `git@${host}:${normalizedPath}`;
        }

        return url; // 其他情况原样返回
    }

    /**
     * 将 Git 仓库地址从 SSH 转换为 HTTPS 格式
     * @param {string} url Git 仓库地址
     * @returns {string} 转换后的地址
     */
    static toHttpUrl(url) {
        if (!url) return url;

        if (/^http?:\/\//i.test(url)) {
            return url;
        }

        const match = url.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
        if (match) {
            const host = match[1];
            const repoPath = match[2];
            const normalizedPath = repoPath.endsWith('.git') ? repoPath : `${repoPath}.git`;
            return `http://${host}/${normalizedPath}`;
        }

        return url;
    }

    /**
     * 判断对象中属性是否存在且有值
     * @param {object} obj - 目标对象
     * @param {string} key - 属性名
     * @returns {boolean} true 表示存在且有值，false 表示不存在或无值
     */
    static hasValue(obj, key) {
        if (!obj || typeof obj !== 'object') return false;
        return obj.hasOwnProperty(key) && obj[key] != null && obj[key] !== '';
    }
    /**
     * 判断字符串是否以 -service 结尾
     * 如果是则替换成 -server，否则返回空字符串
     * @param {string} name
     * @returns {string}
     */
    static serviceToServer(name) {
        if (typeof name !== "string") return "";
        if (name.endsWith("-service")) {
            return name.replace(/-service$/, "-server");
        }
        return "";
    }

    /**
     * 判断对象中属性是否存在且有值
     * @param {object} obj - 目标对象
     * @returns {boolean} true 表示存在且有值，false 表示不存在或无值
     */
    static notNull(obj) {
        if (!obj) return false;
        return obj !== '';
    }
    /**
     * 判断对象中属性是否为空
     * @param {object} obj - 目标对象
     * @returns {boolean} true 表示存在且有值，false 表示不存在或无值
     */
    static isNull(obj) {
        return !StrUtil.notNull(obj);
    }
    /**
     * 工具函数 - 检查空对象
     */
    static isEmptyObject(obj) {
        return Object.keys(obj).length === 0 && obj.constructor === Object;
    }
    /**
     * 工具函数 - 判断数组是否为空
     * @param {any} arr
     * @returns {boolean} true 如果不是数组或数组长度为0
     */
    static isEmptyArray(arr) {
        return !Array.isArray(arr) || arr.length === 0;
    }
    /**
     * 工具函数 - 判断数组是否不为空
     * @param {any} arr
     * @returns {boolean} true 如果不是数组或数组长度为0
     */
    static notEmptyArray(arr) {
        return !this.isEmptyArray(arr);
    }
    /**
     * 从 Maven 命令字符串中获取 -pl 参数值
     * @param {string} cmd Maven 命令字符串
     * @returns {string} -pl 参数对应的值，如果不存在返回空字符串
     */
    static getPlParam(cmd) {
        if (!cmd) return '';
        // 匹配 -pl 后面紧跟的非空字符串（忽略空格）
        const match = cmd.match(/-pl\s+([^\s]+)/);
        return match ? match[1] : '';
    }
    /**
     * 将字符串转换为安全的文件夹名（跨平台）
     * @param {string} name 原始名称
     * @returns {string} 安全的文件夹名
     */
    static toSafeFolderName(name) {
        if (!name) return '';

        // 1. 去掉首尾空格
        let safe = name.trim();

        // 2. 替换非法字符为 "_"
        // Windows 禁止: \ / : * ? " < > |
        // 额外处理: 空格、制表符、换行符、控制字符、以及常见特殊字符 &%$!@#^=+,;'`
        safe = safe.replace(/[\\\/:\*\?"<>\|\s\r\n\t&%$!@#^=+,;'`]/g, '_');

        // 3. 合并连续的下划线为一个
        safe = safe.replace(/_+/g, '_');

        // 4. 去掉开头和结尾的下划线（避免出现 "_xxx_"）
        safe = safe.replace(/^_+|_+$/g, '');

        // 5. 处理保留名（Windows）
        const reserved = [
            'CON', 'PRN', 'AUX', 'NUL',
            'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
            'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
        ];
        if (reserved.includes(safe.toUpperCase())) {
            safe = `_${safe}_`;
        }

        // 6. 文件夹名不能是空
        if (!safe) {
            safe = '_';
        }

        return safe;
    }

    /**
     * 获取最后一个 / 后面的内容
     * @param {string} url
     * @returns {string}
     */
    static getLastPath(url) {
        if (!url) return '';
        const idx = url.lastIndexOf('/');
        return idx >= 0 ? url.substring(idx + 1) : url;
    }
    /**
     * 从 Git 仓库地址中解析仓库名
     * @param {string} url Git 仓库地址
     * @returns {string} 仓库名（去掉 .git 后缀），如果无法解析返回空字符串
     */
    static getRepoName(url) {
        if (!url) return '';
        // 提取最后一个 / 或 : 后的部分
        const match = url.match(/[:\/]([^\/:]+?)(?:\.git)?$/);
        return match ? match[1] : '';
    }

    /**
     * 清洗发布单中的应用名称，去掉接口返回附带的额外拼接信息
     * @param {string} name
     * @returns {string}
     */
    static normalizeOpsAppName(name) {
        if (!name) return '';
        const normalized = String(name).trim();
        if (!normalized) {
            return '';
        }
        const equalIndex = normalized.indexOf('=');
        if (equalIndex === -1) {
            return normalized;
        }
        const candidate = normalized.substring(0, equalIndex).trim();
        return candidate || normalized;
    }

    /**
     * 截取最后一个 '|' 后面的内容
     * 如果没有 '|'，直接返回原字符串
     * @param {string} str
     * @returns {string}
     */
    static getLastDescription(str) {
        if (!str) return '';
        const parts = str.split('|');
        return parts[parts.length - 1] || '';
    }

    /**
     * 格式化状态，使用string-width准确计算显示宽度
     * @param {string} emoji - 表情符号
     * @param {number} targetWidth - 目标宽度
     * @returns {string} 格式化后的状态字符串
     */
    static formatStatus(emoji, targetWidth = 6) {
        const currentWidth = stringWidth(emoji);
        const paddingNeeded = Math.max(0, targetWidth - currentWidth);
        return emoji + ' '.repeat(paddingNeeded);
    }
    /**
     * 截取路径/字符串最后一段
     * @param {string} input 输入字符串
     * @returns {string} 最后一段
     */
    static getLastSegment(input) {
        if (!input) return '';
        const parts = input.split('/');
        return parts[parts.length - 1];
    }

    /**
     * 判断字符串是否包含中文字符
     * @param {string} str
     * @returns {boolean}
     */
    static containsChinese(str) {
        if (!str) return false;
        return /[\u4e00-\u9fa5]/.test(str);
    }

    /**
     * 拆分路径，获取最后一段和前面的部分
     * @param {string} input 输入字符串
     * @returns {{ last: string, before: string[] }}
     */
    static splitPath(input) {
        if (!input) return { last: '', before: [] };

        // 去掉首尾空格和多余的 /
        input = input.trim().replace(/^\/+|\/+$/g, '');
        const parts = input.split('/').filter(Boolean);

        const last = parts.length > 0 ? parts[parts.length - 1] : '';
        const before = parts.slice(0, -1);

        return { last, before };
    }

    /**
     * 解析应用参数
     * 支持：
     *   - 空格分隔: a1 a2
     *   - 逗号分隔: a1,a2
     *   - 混合: a a1,a2
     * @param {string[]} args
     * @returns {string[]} 格式化后的应用名数组
     */
    static parseAppArgs(args) {
        if (!args || args.length === 0) {
            return [];
        }
        // 如果不是数组，包一层
        if (!Array.isArray(args)) {
            args = [];
        }
        return args
            .flatMap(arg => arg.split(",")) // 先按逗号展开
            .map(s => s.trim())             // 去掉空格
            .filter(Boolean);               // 去掉空字符串
    }

    /**
     * 从 GitLab Merge Request 链接中提取 MR ID
     * @param {string} url - GitLab Merge Request 的完整 URL
     * @returns {number|null} MR ID，如果匹配不到则返回 null
     */
    static getMrId(url) {
        // 使用正则匹配 /merge_requests/ 后面的数字
        const match = url.match(/\/merge_requests\/(\d+)/);
        // 如果匹配成功，返回数字；否则返回 null
        return match ? parseInt(match[1], 10) : null;
    }
    /**
     * 解析 SonarQube 日志中的 bugs 和 coverage
     * @param {string} logLine - 单行日志
     * @returns {{bugs: number, coverage: number} | null}
     */
    static parseSonarLog(logLine) {
        const regex = /bugs=(\d+),\s*coverage=([\d.]+)/;
        const match = logLine.match(regex);
        if (!match) return null;
        return {
            bugs: parseInt(match[1], 10),
            coverage: parseFloat(match[2])
        };
    }


    static hyperlink(text, url) {
        const OSC = '\u001B]8;;'; // 开始序列
        const ST = '\u0007';      // 结束符
        return `${OSC}${url}${ST}${text}${OSC}${ST}`;
    }
}
